package util

import (
	"encoding/json"
	"fmt"
	"github.com/rs/xid"
	"github.com/spf13/cobra"
	"github.com/spf13/pflag"
	"gopkg.in/yaml.v3"
	"io/ioutil"
	"operator/pkg/util"
	"os"
	"os/user"
	"path/filepath"
	"runtime"
	"strings"
	"time"
)

const initFileName = ".initcfg.yaml"
const dockerfiledata = `FROM alpine
WORKDIR /home/
COPY ${exec_name} .
${other_file}
ENTRYPOINT ["./${exec_name}"]
`

type RunOS int

const (
	OS_LINUX RunOS = iota
	OS_WINDOWS
)

func (ro RunOS) ToString() string {
	switch ro {
	case 0:
		return "linux"
	case 1:
		return "windows"
	default:
		return "error"
	}
}

type RunArch int

const (
	AMD64 RunArch = iota
	ARM64
)

func (ra RunArch) ToString() string {
	switch ra {
	case 0:
		return "amd64"
	case 1:
		return "arm64"
	default:
		return "error"
	}
}

type CICDConfig struct {
	ID            string        `yaml:"id"`
	TargetOS      RunOS         `yaml:"target_os"`
	TargetArch    RunArch       `yaml:"target_arch"`
	RunnerTimeOut time.Duration `yaml:"runner_time_out"`
	NeedDirOrFile []string      `yaml:"need_dir_or_file"`
	ExecName      string        `yaml:"exec_name"`
	DefaultPath   string        `yaml:"default_path"`
	TempPath      string        `yaml:"temp_path"`
	Version       string        `yaml:"version"`
	ImageName     string        `yaml:"image_name"`
	DockerConfig  string        `yaml:"docker_config"`
	AuthBase      string        `json:"auth_base"`
}

func (c *CICDConfig) BuildDockerFile() error {
	var err error
	dockerfile := filepath.Join(c.DefaultPath, "Dockerfile")
	if _, err = os.Stat(dockerfile); os.IsNotExist(err) {
		f, err := os.Create(dockerfile)
		str := strings.ReplaceAll(dockerfiledata, "${exec_name}", c.ExecName)

		otherStr := ""
		if len(c.NeedDirOrFile) > 0 {
			for _, d := range c.NeedDirOrFile {
				otherStr += "COPY " + d + " .\n"
			}
		}
		str = strings.ReplaceAll(str, "${other_file}", otherStr)

		_, err = f.Write([]byte(str))
		defer f.Close()
		if err != nil {
			return err
		}
	}

	return nil
}

func (c *CICDConfig) Save() error {
	initP := filepath.Join(c.DefaultPath, initFileName)
	f, err := os.Create(initP)
	if err != nil {
		return err
	}

	data, err := yaml.Marshal(c)
	if err != nil {
		return err
	}

	_, err = f.Write(data)
	err = f.Close()
	if err != nil {
		return err
	}

	return err
}

func (c *CICDConfig) SetOS(os RunOS) {
	switch os {
	case OS_LINUX:
		c.TargetOS = os
		if strings.Contains(c.ExecName, ".exe") {
			c.ExecName = strings.ReplaceAll(c.ExecName, ".exe", "")
		}
	case OS_WINDOWS:
		c.TargetOS = os
		if !strings.Contains(c.ExecName, ".exe") {
			c.ExecName += ".exe"
		}
	default:
		return
	}
}

func InitConfig(dockerC string) *CICDConfig {
	tempPath := "temp-dir"

	return &CICDConfig{
		ID:            xid.New().String(),
		TargetOS:      OS_LINUX,
		TargetArch:    AMD64,
		RunnerTimeOut: time.Second * 60,
		NeedDirOrFile: make([]string, 0, 10),
		ExecName:      "run",
		DefaultPath:   "./",
		TempPath:      tempPath,
		DockerConfig:  dockerC,
	}
}

func LoadFromFile(path string) (*CICDConfig, error) {
	path = filepath.Join(path, initFileName)
	f, err := os.Open(path)
	if err != nil {
		return nil, err
	}
	config := &CICDConfig{}
	err = yaml.NewDecoder(f).Decode(config)
	if err != nil {
		return nil, err
	}

	return config, nil
}

func GetConfigFromCmd(cmd *cobra.Command) *CICDConfig {
	flag := cmd.Flags()
	var (
		os1        = OS_LINUX
		arch1      = AMD64
		dpath      = "./"
		exec       = "run"
		dockerC    = "tcp://127.0.0.1:2375"
		imageN     = "run"
		version    = "latest"
		serverAddr = ""
	)

	getStringFromFlag(flag, "targetOs", &os1)
	getStringFromFlag(flag, "targetArch", &arch1)
	getStringFromFlag(flag, "execName", &exec)
	getStringFromFlag(flag, "path", &dpath)
	getStringFromFlag(flag, "host", &dockerC)
	getStringFromFlag(flag, "image", &imageN)
	getStringFromFlag(flag, "version", &version)
	getStringFromFlag(flag, "serverAddr", &serverAddr)

	temp, err := os.MkdirTemp("", xid.New().String())
	if err != nil {
		panic(err)
	}

	str := ""
	pconf, err := LoadPersistentConfigFromFile()
	if err != nil || pconf.Auth == "" {
		fmt.Println("not login,please use login first!")
		os.Exit(1)
	}
	str = pconf.Auth

	return &CICDConfig{
		ID:            xid.New().String(),
		TargetOS:      os1,
		TargetArch:    arch1,
		RunnerTimeOut: time.Second * 60,
		NeedDirOrFile: make([]string, 0, 10),
		ExecName:      exec,
		DefaultPath:   dpath,
		TempPath:      temp,
		DockerConfig:  dockerC,
		ImageName:     imageN,
		Version:       version,
		AuthBase:      str,
	}
}

func getStringFromFlag(set *pflag.FlagSet, flag string, output interface{}) {
	switch output.(type) {
	case *string:
		data, err := set.GetString(flag)
		if err == nil {
			*output.(*string) = data
		}
	case *int:
		data, err := set.GetInt(flag)
		if err == nil {
			*output.(*int) = data
		}
	case *RunOS:
		data, err := set.GetString(flag)
		if err == nil {
			switch data {
			case "linux":
				*output.(*RunOS) = OS_LINUX
			case "windows":
				*output.(*RunOS) = OS_WINDOWS
			}
		}
	case *RunArch:
		data, err := set.GetString(flag)
		if err == nil {
			switch data {
			case "amd64":
				*output.(*RunArch) = AMD64
			case "arm64":
				*output.(*RunArch) = ARM64
			}
		}
	}
}

type PersistentConfig struct {
	Auth       string      `json:"auth"`
	KubeConfig *KubeConfig `json:"kube_config"`
}

func SavePersistentConfigFromFile(config *PersistentConfig) error {
	path, err := getHomePath()
	if err != nil {
		return err
	}

	f, err := os.Create(path)
	if err != nil {
		return err
	}

	_, err = f.Write([]byte(util.JsonEncode(config)))
	if err != nil {
		return err
	}

	return nil
}

func LoadPersistentConfigFromFile() (*PersistentConfig, error) {
	path, err := getHomePath()
	if err != nil {
		return nil, err
	}

	f, err := os.Open(path)
	if err != nil {
		_, err = os.Create(path)
		if err != nil {
			return nil, err
		}
		return &PersistentConfig{}, nil
	}

	data, err := ioutil.ReadAll(f)
	if err != nil {
		return nil, err
	}

	config := &PersistentConfig{}
	err = json.Unmarshal(data, config)
	if err != nil {
		return nil, err
	}

	return config, nil
}

func ExistPersistentConfigFile() bool {
	path, err := getHomePath()
	if err != nil {
		return false
	}

	if _, err = os.Stat(path); err != nil {
		return false
	}

	return true
}

func getHomePath() (string, error) {
	var home string
	if runtime.GOOS == "linux" {
		home = os.Getenv("HOME")
		if home == "" {
			home = "/root"
		}
	} else if runtime.GOOS == "windows" {
		current, err := user.Current()
		if err != nil {
			return "", err
		}
		home = current.HomeDir
	}

	path := filepath.Join(home, ".meli")
	if _, err := os.Stat(path); os.IsNotExist(err) {
		err = os.MkdirAll(path, 0777)
		if err != nil {
			return "", err
		}
	} else if err != nil {
		return "", err
	}

	path = filepath.Join(path, "config.json")

	return path, nil
}
